package com.gxa.filter;

import com.alibaba.fastjson.JSONObject;
import com.gxa.common.JwtResult;
import com.gxa.common.R;
import com.gxa.common.Status;
import com.gxa.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

public class OAuth2Filter extends AuthenticatingFilter {

    /**
     * 访问控制器<br>
     * 验证Token是否可用，并返回对应的消息给客户端<br>
     * 并且终止过滤链，使其无法继续执行过滤链<br>
     *
     * @param request  the incoming <code>ServletRequest</code>
     * @param response the outgoing <code>ServletResponse</code>
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        R result = new R();
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        //获取请求头或者参数中的Token
        String token = getRequestToken(httpServletRequest);
        if (token == null || "".equals(token)) {
            result.fromStatus(Status.NULL_TOKEN);
            writeToClient(result, httpServletResponse);
            //返回后终止
        }
        //验证Token
        JwtResult jwtResult = JwtUtils.validateJwt(token);
        if (jwtResult.isSuccess()) {
            //继续过滤链
            Claims claims = JwtUtils.parseJWT(token);
            UsernamePasswordToken usernamePasswordToken = JSONObject.parseObject(claims.getSubject(), UsernamePasswordToken.class);
            getSubject(request, response).login(usernamePasswordToken);
            return true;
        } else {
            //Json不合法
            if (jwtResult.getErrCode() == JwtUtils.JWT_ERRCODE_EXPIRE) {
                result.fromStatus(Status.TOKEN_EXPIRED);
                writeToClient(result, httpServletResponse);
            }
            //Json验证失败
            if (jwtResult.getErrCode() == JwtUtils.JWT_ERRCODE_FAIL) {
                result.fromStatus(Status.TOKEN_IS_ILLEGAL);
                writeToClient(result, httpServletResponse);
            }
            return false;
        }

    }

    /**
     * 返回给前端Json数据
     *
     * @param o        需要转为JSON数据并发送的实体类
     * @param response 传输Response,用于调用writer返回信息
     */
    private void writeToClient(Object o, ServletResponse response) throws IOException {
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setContentType("application/json;charset=utf-8");
        PrintWriter writer = null;
        try {
            writer = httpServletResponse.getWriter();
            writer.write(JSONObject.toJSONString(o));
            writer.flush();
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException("写回Json到前端时发生IO错误，方法名：writeToClient(Object o, HttpServletResponse httpServletResponse)");
        }
    }

    /**
     * 获取请求的token
     */
    private String getRequestToken(HttpServletRequest httpRequest) {
        //从header中获取token
        String token = httpRequest.getHeader("Authorization");
        //如果header中不存在token，则从参数中获取token
        if (token == null || "".equals(token)) {
            token = httpRequest.getParameter("Authorization");
        }
        return token;
    }


    public OAuth2Filter() {
        super();
    }

    @Override
    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
        return null;
    }

    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
        R result = new R();
        //处理登陆失败的异常
        try {
            Throwable throwable = e.getCause() == null ? e : e.getCause();
            result.fromStatus(Status.SYSTEM_ERROR, throwable.getMessage());
            writeToClient(result, response);
        } catch (IOException ioe) {
            System.out.println("onLoginSuccess 发生了登陆失败的异常");
            ioe.printStackTrace();
        }
        return false;
    }

    /**
     * 解决跨域请求中的Option请求：<br>
     * 进行跨域请求且请求头中有额外参数时，<br>
     * 客户端会先发送一个Option请求来探测后续发起的跨域POST请求是否可以安全和是否可以接收，<br>
     * 所以不用进行拦截<br>
     *
     * @param request     the incoming <code>ServletRequest</code>
     * @param response    the outgoing <code>ServletResponse</code>
     * @param mappedValue the filter-specific config value mapped to this filter in the URL rules mappings.
     * @return
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        HttpServletRequest req = (HttpServletRequest) request;
        if (req.getMethod().equals(RequestMethod.OPTIONS.name())) {
            return true;
        }
        return false;
    }
}
